
import pandas as pd
import numpy as np


# Series   ------------- 一维数据 --------------
# 创建：pd.Series(data, index, dtype)
# 属性：.index, .values, .dtype, .name
# 操作：索引、切片、矢量化运算

# DataFrame
# 创建：pd.DataFrame(data, columns, index)
# 属性：.columns, .index, .shape, .dtypes, .info()
# 行列操作：增删改查（df['col'], df.loc[], df.iloc[]）



# -------------------------------
s = pd.Series({'北京': 2154, '上海': 2428, '广州': 1867})
s[["北京","上海"]]
s[s > 2000]

#  加法操作  --> NaN
s.apply(np.log)


df = pd.DataFrame(
    [[40269, 2154], [43214, 2428], [28232, 1867]],
    columns=['GDP(亿)', '人口(万)'],
    index=['北京', '上海', '广州'],
    dtype=np.int64
)

df.info
df.index
df.dtypes
df.shape
df.columns
df.values

# inplace=True：直接修改原 DataFrame
# errors='ignore'：如果要删除的列/行不存在，不报错
# loc基于标签，iloc基于位置

# 从字典创建（键→列名）
data = {
    '城市': ['北京', '上海', '广州'],
    'GDP(亿)': [40269, 43214, 28232],
    '人口(万)': [2154, 2428, 1867]
}
df = pd.DataFrame(data, index=['A', 'B', 'C'])
#    城市  GDP(亿)  人口(万)
# A  北京   40269   2154
# B  上海   43214   2428
# C  广州   28232   1867



df.drop('列名', axis=1) 或 df.drop(columns='列名')  # 删除列
df.drop(行索引, axis=0) 或 df.drop(index=行索引)    # 删除行
df.dropna(axis=0/1)                                # 删除 NaN
df.drop_duplicates()                               # 删除重复行



df.iloc[0:2, 1:3]     # 位置切片  1,2行 2,3 列
df.loc[['A', 'D'], ['城市', '人均GDP']]  # 行列标签组合

df.loc['A']  # 按索引 取行
df.loc['D'] = ['深圳', 30665, 8.2]  # 增加新行
df['人均GDP'] = df['GDP(亿)'] / df['人口(万)'] * 10000  # 新增列
df[df['GDP(亿)'] > 40000]  # GDP超4万亿的城市

df.set_index(keys, drop=True, append=False, inplace=False, verify_integrity=False)
# keys	            要设置为索引的列名（字符串或列表）
# drop	            是否删除原列（默认 True，即删除）
# append	        是否保留原有索引（默认 False，替换原索引）
# inplace	        是否直接修改原 DataFrame（默认 False，返回新 DataFrame）
# verify_integrity	是否检查新索引是否有重复（默认 False，不检查）



df.reset_index()  #  恢复到原来状态   inplace=True 时不可逆
# Pandas 的索引（Index）是一个独立对象，与数据列分开存储。
# set_index() 实质是将某列数据移动到索引区域。
# reset_index() 则是将索引区域的数据移回列区域





df.isna().sum()
df.dropna(axis=0, how='any', subset=['A', 'C'])



df_dup.duplicated(subset=['Product', 'Price'], keep='first')   # 检测重复值
# 参数：
# - keep='first': 保留首次出现（默认）
# - keep='last': 保留最后一次出现
# - keep=False: 标记所有重复项

# 删除重复值
df_unique = df_dup.drop_duplicates(
    subset=['Product'],  # 按产品去重
    keep='last',         # 保留最后一条记录
    inplace=False
)

# 安全转换（pd.to_numeric）
df_dtype['Price_fixed'] = pd.to_numeric(
    df_dtype['Price'], 
    errors='coerce',  # 将错误转为NaN
    downcast='integer'  # 向下转换节省内存
)


# 自定义转换函数：
df['col'].apply(lambda x: custom_parser(x))





# 字符串处理示例
text_data = {'ID': ['A001', 'B002', 'C003'],
             'Name': ['john DOE', 'Alice Smith', 'BOB JONES'],
             'Email': ['john@test.com', 'alice@demo.net', 'invalid_email']}
df_text = pd.DataFrame(text_data)

# 大小写转换
df_text['Name_upper'] = df_text['Name'].str.upper()

# 包含检测
df_text['is_valid_email'] = df_text['Email'].str.contains(r'@[a-z]+\.(com|net)$', regex=True)

# 正则提取
df_text['Domain'] = df_text['Email'].str.extract(r'@([a-z]+\.\w+)', expand=False)

# 分列操作
df_text[['First', 'Last']] = df_text['Name'].str.split(n=1, expand=True)





# 横向连接索引
# joined = df1.join(df2)




sales = pd.DataFrame({
    'Date': ['2023-01', '2023-01', '2023-02', '2023-02'],
    'Product': ['A', 'B', 'A', 'B'],
    'Revenue': [100, 150, 200, 250],
    'Quantity': [10, 15, 20, 25]
})



# --------------------------- 把多列 转变成 1列 -------------------
pd.melt(
    frame,           # 要融合的DataFrame
    id_vars=None,    # 保持不变的列（标识变量）
    value_vars=None, # 要融合的列（值变量），默认融合所有非id_vars列
    var_name=None,   # 用于存储融合后变量名的列名
    value_name='value',  # 用于存储融合后值的列名，默认为'value'
    col_level=None,      # 如果列是多层索引，指定要融合的层级
    ignore_index=True    # 是否忽略原始索引，默认为True
)

# 原始数据:
#    ID     Name  Math  Physics  Chemistry
# 0   1    Alice    90       88         92
# 1   2      Bob    85       82         88
# 2   3  Charlie    92       95         90

melted = pd.melt(df, id_vars=['ID', 'Name'], 
                value_vars=['Math', 'Physics', 'Chemistry'],
                var_name='Subject', 
                value_name='Score')
# 融合后的数据:
#    ID     Name    Subject  Score
# 0   1    Alice       Math     90
# 1   2      Bob       Math     85
# 2   3  Charlie       Math     92
# 3   1    Alice    Physics     88
# 4   2      Bob    Physics     82
# 5   3  Charlie    Physics     95
# 6   1    Alice  Chemistry     92
# 7   2      Bob  Chemistry     88
# 8   3  Charlie  Chemistry     90



df = pd.DataFrame({
    'Department': ['HR', 'Tech', 'HR', 'Tech'],
    'Employee': ['Alice', 'Bob', 'Charlie', 'David'],
    'Salary': [5000, 8000, 5500, 8500]
})
# grouped : 字典，键为分组名，值为对应行索引
grouped = df.groupby('Department')   # 返回 GroupBy 对象，不立即计算
# 单列聚合
print(grouped['Salary'].mean())
# 多列多函数聚合
agg_result = grouped.agg({
    'Salary': ['sum', 'mean', 'max'],
    'Employee': 'count'
})


df.groupby(by=None, axis=0, level=None, as_index=True, sort=True, 
           group_keys=True, squeeze=False, observed=False, dropna=True)

# by	     分组依据，可以是列名、列名列表、字典、Series或函数
# axis	     分组轴，0表示按行分组，1表示按列分组
# level	     用于多层索引的分组级别
# as_index	 是否将分组列作为结果DataFrame的索引
# sort	     是否对分组键进行排序
# group_keys 在应用函数时是否添加分组键
# squeeze	 如果可能，减少返回类型的维度
# observed	 只显示分类分组中观察到的值
# dropna 	 是否排除分组键中的NA值


# 对薪资和工龄都计算均值和总和
df.groupby('Department')[['Salary', 'Years']].agg(['mean', 'sum'])

result = df_stock.groupby(['Stock', 'Date']).agg(
    avg_price=('Price', 'mean'),   #  结果命名为 avg_price
    volatility=('Price', lambda x: x.max() - x.min()) #  结果命名为 volatility
).reset_index()




df = pd.DataFrame({
    'Age': [22, 35, 47, 62, 28],
    'Income': [30000, 60000, 80000, 120000, 45000]
})

print(df)
print("-----------------------")

# 按年龄分组统计平均收入
age_bins = [20, 30, 40, 50, 70]
df['AgeGroup'] = pd.cut(df['Age'], bins=age_bins)
income_by_age = df.groupby('AgeGroup')['Income'].mean()
print(income_by_age)
#    Age  Income
# 0   22   30000
# 1   35   60000
# 2   47   80000
# 3   62  120000
# 4   28   45000
# -----------------------
# AgeGroup
# (20, 30]     37500.0
# (30, 40]     60000.0
# (40, 50]     80000.0
# (50, 70]    120000.0
# Name: Income, dtype: float64



# size()：计算每组的总行数（包括所有列，即使有 NaN 值）
# count()：计算每组中非NA值的数量（按列统计）

# float('inf') 表示正无穷大




dates = pd.date_range(start='2022-01-01', end='2022-12-31')
df['星期'] = df['Date'].dt.day_name()

df.groupby(['耗电等级', '星期']).size().unstack()
# .unstack()  将行索引转换成列，它可以将内层的行索引"旋转"到列方向，实现数据重塑
#         将多层索引的 Series 转换为 DataFrame
        # level：指定要展开的索引层级（默认 -1，即最内层）
        # fill_value：用于填充缺失值的值（默认 NaN）


# 创建带名称的索引  ---------------示例----------------
df = pd.DataFrame({
    'value': [10, 20, 30, 40],
    'group': ['G1', 'G1', 'G2', 'G2'],
    'category': ['C1', 'C2', 'C1', 'C2']
}).set_index(['group', 'category'])  #  set_index 将原来的列变成索引

# 取消堆叠 'category' 层级
df_unstacked = df.unstack(level='category')   # 把 category 的所有值 提到 1 行，变成列（类似列名）







# -------------------------------- 优化 ------------------------------
# 数值类型降级：将默认的int64/float64降级为最小兼容类型（如int8/float32），内存占用可减少50%~70%
# 稀疏数据结构：对高缺失值列（>90%）使用SparseArray，避免存储冗余值
# 避免链式索引：使用loc或iloc单步赋值，避免中间副本生成: df.loc[df.price > 100, 'discount'] = 0.9

# 优化2：分类类型转换（内存减少5-10倍）
df["category"] = df["category"].astype("category")  # 仅存储唯一值的索引


# 转换为SparseArray
df["sparse_col"] = df["sparse_col"].astype(pd.SparseDtype("float"))








# Pandas替代方案深度对比
import dask.dataframe as dd

# 分块读取100GB CSV（单机内存仅16GB）
ddf = dd.read_csv("transactions_3y.csv", blocksize="64MB")  # 分割为64MB块 

# 惰性计算图构建（无立即执行）
ddf["discount"] = ddf["price"] * 0.9
result = ddf.groupby("user_id")["discount"].mean()

# 触发并行计算（自动利用多核）
result = result.compute(scheduler="processes")  # 显式触发执行 
# 兼容Pandas API，零代码迁移成本
# 单机多核/集群分布式扩展
# 局限：索引操作性能较差（如set_index可能触发全量洗牌



# Polars（基于Rust的高性能引擎）
import polars as pl

# 读取数据（比Pandas快10倍）
df_pl = pl.read_csv("transactions_3y.csv")

# 链式表达式优化
result = (
    df_pl.lazy()  # 启用延迟执行
    .filter(pl.col("price") > 100)  # 过滤高价订单
    .groupby("user_id")
    .agg(pl.col("discount").mean())  # 聚合计算
    .collect()  # 触发实际执行
)

# 强制使用多线程（默认已启用）
pl.set_thread_pool_size(8)  # 指定8线程 
# 零拷贝内存管理（Apache Arrow格式）
# SIMD指令优化与多线程并行





# Modin（自动并行化Pandas）
# 仅需替换import语句
import modin.pandas as mpd  # 替代import pandas as pd

df = mpd.read_csv("large_data.csv")  # 自动分块并行读取
result = df.groupby("category")["value"].sum()  # 无代码修改
# 8核机器处理1亿行数据时，read_csv比Pandas快4倍 
# 无需修改现有Pandas代码





# 向量方位为什么快：
# 1. 硬件指令优化，一条指令操作多条数据
# 2. 内存访问，连续的内存访问 
# 3. 绕开python解释器动态类型检查和函数调用